package com.xxx.mall.manager;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.xxx.mall.bo.AuthAccountBO;
import com.xxx.mall.constant.CacheNames;
import com.xxx.mall.constant.SysTypeEnum;
import com.xxx.mall.exception.MallException;
import com.xxx.mall.response.ResponseEnum;
import com.xxx.mall.vo.TokenInfoVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * @author 邢晨旭
 * {@code @date} 2023/11/15
 */
@Component
@Slf4j
@AllArgsConstructor
public class TokenStore {

    private final RedisTemplate<Object, Object> redisTemplate;
    private final RedisSerializer<Object> redisSerializer;
    private final StringRedisTemplate stringRedisTemplate;


    public TokenInfoVO storeAndGetVo(AuthAccountBO authAccount) {
        return storeAccessToken(authAccount);
    }

    public TokenInfoVO storeAccessToken(AuthAccountBO authAccount) {
        TokenInfoVO tokenInfoVO = new TokenInfoVO();
        String accessToken = IdUtil.fastSimpleUUID();
        String refreshToken = IdUtil.fastSimpleUUID();
        long expiresIn = getExpiresIn(authAccount.getSysType());
        tokenInfoVO.setExpiresIn(expiresIn);
        //获取key
        String uidToAccessKeyStr = getUidToAccessKey(getApprovalKey(authAccount.getSysType().toString(), authAccount.getUid().toString()));
        String accessKeyStr = getAccessKey(accessToken);
        String refreshToAccessKeyStr = getRefreshToAccessKey(refreshToken);
        String refreshToUserStr = getRefreshToUserKey(refreshToken);

        //存储token
        redisTemplate.executePipelined((RedisCallback<?>) connection -> {
            //byte[] uidKey = uidToAccessKeyStr.getBytes(StandardCharsets.UTF_8);
            byte[] accessKey = accessKeyStr.getBytes(StandardCharsets.UTF_8);
            byte[] refreshKey = refreshToAccessKeyStr.getBytes(StandardCharsets.UTF_8);
            byte[] refreshToUserKey = refreshToUserStr.getBytes(StandardCharsets.UTF_8);
            //通过refresh_token获取用户的access_token从而刷新token
            byte[] userByte = Objects.requireNonNull(redisSerializer.serialize(authAccount));
            connection.setEx(refreshKey, expiresIn * 7, accessToken.getBytes(StandardCharsets.UTF_8));
            connection.setEx(refreshToUserKey, expiresIn * 7, userByte);
            //根据access_token获取用户信息
            connection.setEx(accessKey, expiresIn, userByte);
            return null;
        });
        tokenInfoVO.setAccessToken(accessToken);
        tokenInfoVO.setRefreshToken(refreshToken);
        return tokenInfoVO;
    }

    private static String getApprovalKey(String appId, String uid) {
        return uid == null ? appId : appId + StrUtil.COLON + uid;
    }

    public String getUidToAccessKey(String approvalKey) {
        return CacheNames.UID_TO_ACCESS + approvalKey;
    }

    public String getAccessKey(String accessToken) {
        return CacheNames.ACCESS + accessToken;
    }

    public String getRefreshToAccessKey(String refreshToken) {
        return CacheNames.REFRESH_TO_ACCESS + refreshToken;
    }

    public String getRefreshToUserKey(String refreshToken) {
        return CacheNames.REFRESH_TO_USER + refreshToken;
    }

    private int getExpiresIn(int sysType) {
        // 3600秒 1小时
        int expiresIn = 3600;

        // 普通用户token过期时间 12小时
        if (Objects.equals(sysType, SysTypeEnum.ORDINARY.value())) {
            expiresIn = expiresIn * 12;
        }
        // 系统管理员的token过期时间 24小时
        if (Objects.equals(sysType, SysTypeEnum.SHOP.value()) || Objects.equals(sysType, SysTypeEnum.PLATFORM.value())) {
            expiresIn = expiresIn * 24;
        }
        return expiresIn;
    }

    public AuthAccountBO getUserInfoByAccessToken(String accessToken) {
        if (StrUtil.isBlank(accessToken)) {
            throw new MallException("accessToken为空");
        }
        Object userInfo = redisTemplate.opsForValue().get(getAccessKey(accessToken));
        if (userInfo == null) {
            throw new MallException(ResponseEnum.ACCESS_TOKEN_OVERDUE);
        }
        return (AuthAccountBO) userInfo;
    }

    private AuthAccountBO getUserInfoByRefreshToken(String refreshToken) {
        Object userInfo = redisTemplate.opsForValue().get(getRefreshToUserKey(refreshToken));
        if (userInfo == null) {
            throw new MallException(ResponseEnum.UNAUTHORIZED);
        }
        return (AuthAccountBO) userInfo;
    }

    public TokenInfoVO refreshToken(String refreshToken) {
        String accessToken = stringRedisTemplate.opsForValue().get(getRefreshToAccessKey(refreshToken));
        if (StrUtil.isBlank(accessToken)) {
            throw new MallException(ResponseEnum.UNAUTHORIZED);
        }
        AuthAccountBO authAccountBO = getUserInfoByRefreshToken(refreshToken);
        //删除旧的token
        redisTemplate.delete(getAccessKey(accessToken));
        redisTemplate.delete(getRefreshToUserKey(refreshToken));
        redisTemplate.delete(getRefreshToAccessKey(refreshToken));
        //生成新的token
        return storeAccessToken(authAccountBO);
    }
}
